/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contains: IPC
 *
 * Change Logs:
 * Date           Author            Notes
 * 2023-07-23     kangjinci           Init
 */

#include <vfsc.h>
#include <nxos.h>

#define VFS_BUF_SIZE 4096

static int VfsClientTls = -1;

static inline NX_IpcClient *Get_VFS_Ipc(void)
{
    NX_Error err;

    NX_IpcClient *client = NX_TlsGetValue(VfsClientTls);
    if (client == NX_NULL)
    {
        client = NX_MemAlloc(sizeof(NX_IpcClient));
        if (!client) {
            NX_Printf("ipc client alloc failed!\n");
            return client;
        }
        err = NX_TlsSetValue(VfsClientTls, client);
        if (err != NX_EOK)
        {
            NX_Printf("ipc client tls set value failed!\n");
            NX_MemFree(client);
            return NX_NULL;
        }

        err = NX_IpcConnect("vfs", Get_VFS_Ipc(), VFS_BUF_SIZE);
        if (err != NX_EOK)
        {
            NX_Printf("ipc client connect to server failed!\n");
            NX_TlsSetValue(VfsClientTls, NX_NULL);
            NX_MemFree(client);
            return NX_NULL;
        }
    }
    return client;
}

NX_PRIVATE void InitStdio(void)
{
    NX_Solt fd, stdfd;
    fd = NX_FileOpen("/System/Device/console", NX_FILE_RDONLY, 0);
    NX_ASSERT(fd >= 0);
    stdfd = NX_SoltCopyTo(NX_ThreadGetCurrent(), fd, 0);
    NX_ASSERT(stdfd == 0);
    if (stdfd != 0)
    {
        NX_ProcessExit(0);
    }
    NX_FileClose(fd);

    fd = NX_FileOpen("/System/Device/console", NX_FILE_WRONLY, 0);
    NX_ASSERT(fd >= 0);
    stdfd = NX_SoltCopyTo(NX_ThreadGetCurrent(), fd, 1);
    NX_ASSERT(stdfd == 1);
    if (stdfd != 1)
    {
        NX_ProcessExit(0);
    }

    stdfd = NX_SoltCopyTo(NX_ThreadGetCurrent(), fd, 2);
    NX_ASSERT(stdfd == 2);
    if (stdfd != 2)
    {
        NX_ProcessExit(0);
    }
    NX_FileClose(fd);
}

void InitVFS_Client(void)
{
    /* alloc net tls */
    VfsClientTls = NX_TlsAlloc();
    if (VfsClientTls < 0)
    {
        NX_Printf("Alloc net tls failed!\n");
        NX_ThreadExit(0);
    }
    /* reinit stdio */
    InitStdio();
}

NX_INITCALL(InitVFS_Client);

NX_Error NX_FileSystemMount(const char * dev, const char * dir, const char * fsname, NX_U32 flags)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(mount), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_MOUNT;

    struct VFS_MountArgs *args = &fr->vfs_mount_args;
    NX_StrCopyN(args->dev, dev, NX_FILE_MAX_PATH);
    NX_StrCopyN(args->dir, dir, NX_FILE_MAX_PATH);
    NX_StrCopyN(args->fsname, fsname, 32);
    args->flags = flags;

    NX_Error err = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);
    return err;
}

NX_Error NX_FileSystemUnmount(const char * path)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(unmount), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_UNMOUNT;

    struct VFS_UnmountArgs *args = &fr->vfs_unmount_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);
    NX_Error err = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);
    return err;
}

NX_Error NX_FileSystemSync(void)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(sync), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_SYNC;

    int ret = NX_IpcCall(Get_VFS_Ipc(), msg);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Solt NX_FileOpen(const char *path, NX_U32 flags, NX_U32 mode)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(open), 0);
    if (!msg)
        return NX_SOLT_INVALID_VALUE;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_OPEN;

    struct VFS_OpenArgs *args = &fr->vfs_open_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);
    args->flags = flags;
    args->mode = mode;

    NX_Solt solt = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);
    return solt;
}

NX_Error NX_FileClose(NX_Solt solt)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(close), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_CLOSE;

    struct VFS_CloseArgs *args = &fr->vfs_close_args;
    args->solt = solt;

    int ret = NX_IpcCall(Get_VFS_Ipc(), msg);
    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Size NX_FileRead(NX_Solt solt, void *buf, NX_Size len)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(read) + len, 0);
    NX_Solt shm = NX_SOLT_INVALID_VALUE;
    NX_Solt shmMap = NX_SOLT_INVALID_VALUE;
    void *mapBuf = NX_NULL;
    if (!msg)
    {
        msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(read), 1);
        if (!msg)
        {
            return 0;
        }

        /* create a share memory */
        shm = NX_ShareMemOpen(NX_NULL, len, NX_SHAREMEM_ANONYMOUS);
        if (shm == NX_SOLT_INVALID_VALUE)
        {
            NX_IpcDestroyMsg(msg);
            return 0;
        }
        mapBuf = NX_ShareMemMap(shm, &shmMap);
        if (mapBuf == NX_NULL)
        {
            NX_ShareMemClose(shm);
            NX_IpcDestroyMsg(msg);
            return 0;
        }
        NX_IpcSetMsgSolt(msg, 0, shm);
    }

    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_READ;

    struct VFS_ReadArgs *args = &fr->vfs_read_args;
    args->solt = solt;
    args->len = len;

    int ret = NX_IpcCall(Get_VFS_Ipc(), msg);
    if (mapBuf != NX_NULL)
    {
        NX_MemCopy(buf, mapBuf, len);
    }
    else
    {
        NX_MemCopy(buf, args->buf, len);
    }

    if (mapBuf != NX_NULL)
    {
        NX_ShareMemUnmap(shmMap);
        NX_ShareMemClose(shm);
    }

    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Size NX_FileWrite(NX_Solt solt, void *buf, NX_Size len)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(write) + len, 0);
    NX_Solt shm = NX_SOLT_INVALID_VALUE;
    NX_Solt shmMap = NX_SOLT_INVALID_VALUE;
    void *mapBuf = NX_NULL;
    if (!msg)
    {
        msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(write), 1);
        if (!msg)
        {
            return 0;
        }

        /* create a share memory */
        shm = NX_ShareMemOpen(NX_NULL, len, NX_SHAREMEM_ANONYMOUS);
        if (shm == NX_SOLT_INVALID_VALUE)
        {
            NX_IpcDestroyMsg(msg);
            return 0;
        }
        mapBuf = NX_ShareMemMap(shm, &shmMap);
        if (mapBuf == NX_NULL)
        {
            NX_ShareMemClose(shm);
            NX_IpcDestroyMsg(msg);
            return 0;
        }
        NX_IpcSetMsgSolt(msg, 0, shm);
    }

    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_WRITE;

    struct VFS_WriteArgs *args = &fr->vfs_write_args;
    args->solt = solt;
    if (mapBuf != NX_NULL)
    {
        NX_MemCopy(mapBuf, buf, len);
    }
    else
    {
        NX_MemCopy(args->buf, buf, len);
    }

    args->len = len;

    int ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    if (mapBuf != NX_NULL)
    {
        NX_ShareMemUnmap(shmMap);
        NX_ShareMemClose(shm);
    }

    NX_IpcDestroyMsg(msg);
    return ret;
}

NX_Error NX_FileIoctl(NX_Solt solt, NX_U32 cmd, void *arg)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(ioctl), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_IOCTL;

    struct VFS_IoctlArgs *args = &fr->vfs_ioctl_args;
    args->solt = solt;
    args->cmd = cmd;
    args->arg = arg;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    return ret;
}

NX_Offset NX_FileSetPointer(NX_Solt solt, NX_Offset offset, int whence)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(lseek), 0);
    if (!msg)
        return 0;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_LSEEK;

    struct VFS_LseekArgs *args = &fr->vfs_lseek_args;
    args->solt = solt;
    args->off = offset;
    args->whence = whence;

    NX_Offset ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileSync(NX_Solt solt)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(fsync), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_FSYNC;

    struct VFS_FsyncArgs *args = &fr->vfs_fsync_args;
    args->solt = solt;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileSetMode(NX_Solt solt, NX_U32 mode)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(fchmod), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_FCHMOD;

    struct VFS_FchmodArgs *args = &fr->vfs_fchmod_args;
    args->solt = solt;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileGetStat(NX_Solt solt, NX_FileStatInfo *st)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(fstat), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_FSTAT;

    struct VFS_FstatArgs *args = &fr->vfs_fstat_args;
    args->solt = solt;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);
    *st = args->st;

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Solt NX_DirOpen(const char *name)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(opendir), 0);
    if (!msg)
        return NX_SOLT_INVALID_VALUE;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_OPENDIR;

    struct VFS_OpenDirArgs *args = &fr->vfs_opendir_args;
    NX_StrCopyN(args->name, name, NX_FILE_MAX_PATH);

    NX_Solt ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_DirClose(NX_Solt solt)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(closedir), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_CLOSEDIR;

    struct VFS_CloseDirArgs *args = &fr->vfs_closedir_args;
    args->solt = solt;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_DirRead(NX_Solt solt, NX_Dirent *dir)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(readdir), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_READDIR;

    struct VFS_ReadDirArgs *args = &fr->vfs_readdir_args;
    args->solt = solt;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);
    *dir = args->dir;

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_DirResetPointer(NX_Solt solt)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(rewinddir), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_REWINDDIR;

    struct VFS_RewindDirArgs *args = &fr->vfs_rewinddir_args;
    args->solt = solt;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_DirCreate(const char *path, NX_U32 mode)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(mkdir), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_MKDIR;

    struct VFS_MkdirArgs *args = &fr->vfs_mkdir_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);
    args->mode = mode;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_DirDelete(const char *path)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(rmdir), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_RMDIR;

    struct VFS_RmDirArgs *args = &fr->vfs_rmdir_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileRename(const char *src, const char *dst)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(rename), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_RENAME;

    struct VFS_RenameArgs *args = &fr->vfs_rename_args;
    NX_StrCopyN(args->src, src, NX_FILE_MAX_PATH);
    NX_StrCopyN(args->dst, dst, NX_FILE_MAX_PATH);

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileDelete(const char *path)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(unlink), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_UNLINK;

    struct VFS_UnlinkArgs *args = &fr->vfs_unlink_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileAccess(const char *path, NX_U32 mode)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(access), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_ACCESS;

    struct VFS_AccessArgs *args = &fr->vfs_access_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);
    args->mode = mode;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileSetModeToPath(const char *path, NX_U32 mode)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(chmod), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_CHMOD;

    struct VFS_ChmodArgs *args = &fr->vfs_chmod_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);
    args->mode = mode;

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    NX_IpcDestroyMsg(msg);

    return ret;
}

NX_Error NX_FileGetStatFromPath(const char *path, NX_FileStatInfo *st)
{
    NX_IpcMsg *msg = NX_IpcCreateMsg(Get_VFS_Ipc(), GET_FS_REQUEST_SIZE(stat), 0);
    if (!msg)
        return NX_ENOMEM;
    VFS_IpcReq *fr = (VFS_IpcReq *)NX_IpcGetMsgData(msg);
    fr->type = VFS_REQ_STAT;

    struct VFS_StatArgs *args = &fr->vfs_stat_args;
    NX_StrCopyN(args->path, path, NX_FILE_MAX_PATH);

    NX_Error ret = NX_IpcCall(Get_VFS_Ipc(), msg);

    *st = args->st;

    NX_IpcDestroyMsg(msg);

    return ret;
}
